package gateway

import (
	"errors"
	"fmt"
	"github.com/gorilla/websocket"
	"io"
	"server/component"
	"server/im/entity"
	"server/im/protocol"
	"sync"
	"time"
)

const (
	loginWait = 60 * time.Second
	// Time allowed to read the next pong message from the peer.
	pongWait = 50 * time.Second

	// Send pings to peer with this period. Must be less than pongWait.
	pingPeriod = 60 * time.Second
)

func newClientItem(user *entity.UserModel, conn GateWayConn, gateway GateWay) ClientItem {
	return ClientItem{
		stopChan:  make(chan struct{}),
		user:      user,
		conn:      conn,
		WriteChan: make(chan entity.GatewayOutModel, 1024),
		gateWay:   gateway,
		wg:        new(sync.WaitGroup),
	}
}

type ClientItem struct {
	stopChan   chan struct{}
	UserUniId  string
	user       *entity.UserModel
	conn       GateWayConn
	WriteChan  chan entity.GatewayOutModel
	gateWay    GateWay
	pingStatus int
	wg         *sync.WaitGroup
}

func (o *ClientItem) Work() {
	defer func() {
		fmt.Println("close", o.conn)
		if o.conn != nil {
			o.conn.CloseFn()
			fmt.Println("close done")
		}
	}()

	go o.readPump()
	go o.writePump()
	go o.beat()
	o.wg.Add(3)
	o.wg.Wait()
	fmt.Println(o.UserUniId + "work done")
}

func (o *ClientItem) Stop() {
	close(o.stopChan)
}

func (o *ClientItem) readPump() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("f1", err)
			component.Log.Error(o.UserUniId, err)
		}
		fmt.Println("stop read")
		o.wg.Done()
	}()
	readChan := make(chan entity.GatewayInModel, 1024)
	errChan := make(chan error, 2)
	go o.conn.DoRead(readChan, errChan, o.stopChan)
READ:
	for {
		select {
		case <-o.stopChan:
			fmt.Println("read break")
			break READ
		case err := <-errChan:
			component.Log.Error(fmt.Errorf("read err: %w", err))
			if errors.Is(err, io.EOF) {
				o.lostConnect()
			} else if websocket.IsCloseError(err, websocket.CloseNoStatusReceived) {
				fmt.Println(fmt.Errorf("客户端未发送状态码关闭连接: %w", err))
				o.lostConnect()
			}
			break READ
		case input := <-readChan:
			if input.Cmd == protocol.ReqPong {
				o.pingStatus = 0
				continue READ
			}
			//input log
			component.MsgLogger.AddMsg(map[string]any{
				"gateway": o.conn.GetGateWay().GetUniId(),
				"input":   input,
			})

			if o.user != nil {
				input.User = *o.user
			}
			input.UserUni = o.UserUniId
			o.gateWay.GetGatewayInputChan() <- input
		}
	}
}

func (o *ClientItem) writePump() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("f2", err)
		}
		fmt.Println("stop write")
		o.wg.Done()
	}()
WRITE:
	for {
		select {
		case <-o.stopChan:
			fmt.Println("write break")
			break WRITE
		case outputModel := <-o.WriteChan:
			//out log
			component.MsgLogger.AddMsg(map[string]any{
				"gateway": o.conn.GetGateWay().GetUniId(),
				"output":  outputModel,
			})

			o.conn.WriteFn(outputModel)
		}
	}
}

func (o *ClientItem) beat() {
	ticker := time.NewTicker(pingPeriod)
	defer func() {
		ticker.Stop()
		fmt.Println("stop beat")
		o.wg.Done()
	}()
BEAT:
	for {
		select {
		case <-o.stopChan:
			fmt.Println("beat break")
			break BEAT
		case t := <-ticker.C:
			fmt.Println("beat", t)
			ping := entity.GatewayOutModel{
				Data: nil,
				Cmd:  protocol.RespPing,
			}
			o.conn.WriteFn(ping)
			o.pingStatus = 1
			timer := time.NewTimer(pongWait)
			select {
			case <-timer.C:
				//fmt.Println("ping deadline-->", carbon.Now().ToDateTimeString())
				if o.pingStatus == 1 {
					//fmt.Println("ping deadline--> done", carbon.Now().ToDateTimeString())
					o.lostConnect()
					break BEAT
				}
			}
		}
	}
}

func (o *ClientItem) lostConnect() {

	o.gateWay.GetClientChan() <- entity.GatewayClientEvent{
		UserUni:   o.UserUniId,
		Data:      nil,
		EventType: entity.ClientEventLostConnect,
	}
}
